
package todolist.logic.parser;

import java.util.Date;
import todolist.entity.DateTimeEntity;
import todolist.personalization.DateTimeDescriptionCustomizer;
import todolist.personalization.ErrorMessages;
import todolist.util.Utils;

/**
 * Parser of a Date/Time Description.
 * @author Ivan
 */
public class DateTimeParser {
	
	private GenericParser _objGP;
	private DateParser _objDP;
	
	public DateTimeParser() {
		_objGP = new GenericParser();
		_objDP = new DateParser();
	}

	/**
	 * Tries to parse String containing an entire Date/Time Description. Caller should check for Error in Parse Result.
	 * Return value never null, even if error.
	 * Error field of Parse Result is set to one of the ErrorMessages.
	 */
	public DateTimeParseResult parseDTDescription(String pstrInput) {
		DateTimeParseResult objDTParseResult = new DateTimeParseResult();
		// valid no-match conditions
		if (pstrInput == null)
			return objDTParseResult;
		pstrInput = pstrInput.trim();
		if (pstrInput.length() == 0)
			return objDTParseResult;
		
		// try to match single DT, i.e. end DT only
		DateTimeEntity entSingleEndDT = parseSingleDT(pstrInput);
		if (entSingleEndDT != null) {
			objDTParseResult.setEndDT(entSingleEndDT);
			return objDTParseResult;
		}
		
		// otherwise, keep parsing, search for date a word at a time, till no words left
		while (pstrInput.length() != 0) {
			// try to parse start DT
			String strMatchResult = _objGP.findMatch(DateTimeDescriptionCustomizer.getInstance().getDTInputStart(), pstrInput);
			if (strMatchResult != null) { // if Start DT keyword found
				if (objDTParseResult.getStartDT() != null) { // if Start DT already exists
					objDTParseResult.setError(ErrorMessages.ERR_DUPLICATE_START_DATE);
					return objDTParseResult;
				}
				pstrInput = strMatchResult;
				DateTimeEntity entStartDT = parseSingleDT(pstrInput);
				if (entStartDT == null) { // if Start DT is missing Date and/or Time information
					objDTParseResult.setError(ErrorMessages.ERR_MISSING_START_DATE);
					return objDTParseResult;
				}
				objDTParseResult.setStartDT(entStartDT);
			}
			strMatchResult = _objGP.findMatch(DateTimeDescriptionCustomizer.getInstance().getDTInputEnd(), pstrInput);
			// try to parse end DT
			if (strMatchResult != null) { // if End DT keyword found
				if (objDTParseResult.getEndDT() != null) { // if End DT already exists
					objDTParseResult.setError(ErrorMessages.ERR_DUPLICATE_END_DATE);
					return objDTParseResult;
				}
				pstrInput = strMatchResult;
				DateTimeEntity entEndDT = parseSingleDT(pstrInput);
				if (entEndDT == null) { // if End DT is missing Date and/or Time information
					objDTParseResult.setError(ErrorMessages.ERR_MISSING_END_DATE);
					return objDTParseResult;
				}
				objDTParseResult.setEndDT(entEndDT);
			}
			pstrInput = Utils.discardNWordsFromString(pstrInput, 1);
		}
		// there is definitely input at this point, check for no Date being parsed
		if (objDTParseResult.getStartDT() == null && objDTParseResult.getEndDT() == null)
			objDTParseResult.setError(ErrorMessages.ERR_INVALID_DATE_PARSING);
		return objDTParseResult;
	}
	
	// private methods
	
	/**
	 * Tries to parse String containing a single DT Description.
	 * If no Date and/or Time information, null returned.
	 */
	private DateTimeEntity parseSingleDT(String pstrInput) {
		String strMatchResult = _objGP.findMatch(DateTimeDescriptionCustomizer.getInstance().getDTInputNonStrict(), pstrInput);
		boolean blnIsStrict = (strMatchResult == null);
		if (strMatchResult != null)
			pstrInput = strMatchResult; // cut down to remaining unparsed input
		return parseTemporalInfoWithDescriptor(pstrInput, blnIsStrict);
	}
	
	/**
	 * Tries to parse String containing DT Descriptor, along with Date and/or Time information.
	 * If no Date and/or Time information, null returned.
	 */
	private DateTimeEntity parseTemporalInfoWithDescriptor(String pstrInput, boolean pblnIsStrict) {
		String[] arrDTDescrs = DateTimeDescriptionCustomizer.getInstance().getDTInputDescriptors();
		String strFoundDTDescr = ""; // empty descriptor by default
		for (String strCurrDTDescr : arrDTDescrs) {
			String[] arrCurrDTDescr = new String[1]; // test each Descriptor individually as there is a need to find out which one it is
			arrCurrDTDescr[0] = strCurrDTDescr;
			String strMatchResult = _objGP.findMatch(arrCurrDTDescr, pstrInput);
			if (strMatchResult == null)
				continue; // if no match, try next Descriptor
			strFoundDTDescr = strCurrDTDescr;
			pstrInput = strMatchResult; // cut down to remaining unparsed input
		}
		return parseTemporal(pstrInput, pblnIsStrict, strFoundDTDescr);
	}
	
	/**
	 * Tries to parse String containing Date and/or Time information only.
	 * If no match found, null returned.
	 */
	private DateTimeEntity parseTemporal(String pstrInput, boolean pblnIsStrict, String pstrDescriptor) {
		Date dtmDate = _objDP.parseDateTime(pstrInput); // match all Date+Time
		if (dtmDate != null)
			return new DateTimeEntity(dtmDate, true, pblnIsStrict, pstrDescriptor);
		dtmDate = _objDP.parseDateOnly(pstrInput); // match all Date without Time
		if (dtmDate != null)
			return new DateTimeEntity(dtmDate, false, pblnIsStrict, pstrDescriptor);
		return null; // no match
	}
	
}
